package cn.dongqihong.framework;

import java.util.*;

/**
 * 简单模板工具类
 * Created by DongQihong on 2016/8/29.
 */

public class Template {

    List<Exp> exps = new ArrayList<>();

    /**
     * 私有构造器，只允许自己构造
     */
    private Template() {
    }

    /**
     * 通过模板字符串获取模板对象
     *
     * @param templStr 模板字符串
     * @return 模板对象
     */
    public static Template getTemplate(String templStr) {
        Objects.requireNonNull(templStr, "模板为空");

        Template templ = new Template();
        StringBuilder sb = new StringBuilder();

        char[] chars = templStr.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            switch (chars[i]) {
                case '$':
                    if (i < chars.length - 1 && chars[i + 1] == '{') {
                        templ.addExp(Exp.getExp(sb.toString()));
                        sb.setLength(0);
                    }
                    sb.append(chars[i]);
                    break;
                case '}':
                    sb.append('}');
                    if (sb.length() > 1 && sb.charAt(1) == '{') {
                        templ.addExp(Exp.getExp(sb.toString()));
                        sb.setLength(0);
                    }
                    break;
                default:
                    sb.append(chars[i]);
                    break;
            }
        }

        if (sb.length() > 0) {
            templ.addExp(Exp.getExp(sb.toString()));
        }

        return templ;
    }

    /**
     * 添加表达式
     *
     * @param exp 表达式
     * @return 模板对象
     */
    private Template addExp(Exp exp) {
        Objects.requireNonNull(exp, "表达式为空");
        exps.add(exp);
        return this;
    }

    /**
     * 判断是否有变量表达式
     * @return
     */
    public boolean hasVarExp() {
        return exps.size() > 1;
    }

    /**
     * 渲染
     *
     * @param context
     * @return
     */
    public String render(Map<String, Object> context) {
        StringBuilder sb = new StringBuilder(128);
        for (Exp exp : exps) {
            sb.append(exp.resolve(context));
        }
        return sb.toString();
    }

    /**
     * 测试方法
     *
     * @param args
     */
    public static void main(String[] args) {
        String t1 = "你好 #{name}, 您的验证码是:#{code}";
        String t2 = "你好 ${name}, 您的验证码是:${code}";

        Map<String, Object> param = new HashMap();
        param.put("name", "haogrgr");
        param.put("code", "1314");

        String result1 = "";
        String result2 = "";
        long start = 0;

        start = System.currentTimeMillis();
        Template templ1 = Template.getTemplate(t1);
        Template templ2 = Template.getTemplate(t2);
        for (int i = 0; i < 1000000; i++) {
            param.put("code", i);
            result1 = templ1.render(param);
            result2 = templ2.render(param);
        }
        System.out.println(System.currentTimeMillis() - start);
        System.out.println(result1);
        System.out.println(result2);
    }

}

/**
 * 表达式
 */
abstract class Exp {

    abstract String resolve(Map<String, Object> context);

    /**
     * 通过表达式字符串获取表达式对象
     *
     * @param exp 表达式字符串
     * @return 表达式对象
     */
    public static Exp getExp(String exp) {
        Objects.requireNonNull(exp);
        if (exp.startsWith("${")) {
            return new VarExp(exp);
        }
        return new StrExp(exp);
    }
}

/**
 * 字符串表达式
 */
class StrExp extends Exp {

    String value;

    StrExp(String exp) {
        this.value = exp;
    }

    @Override
    public String resolve(Map<String, Object> context) {
        return this.value;
    }

    @Override
    public String toString() {
        return "StrExp [value=" + value + "]";
    }

}

/**
 * 变量表达式
 */
class VarExp extends Exp {

    String varName;
    String defaultValue;
    Boolean nullable = false;

    /**
     * 构造变量表达式
     *
     * @param varName      变量名
     * @param defaultValue 默认值
     * @param nullable     是否允许为空
     */
    VarExp(String varName, String defaultValue, Boolean nullable) {
        this.varName = varName;
        this.defaultValue = defaultValue;
        this.nullable = nullable;
    }

    /**
     * 构造变量表达式
     *
     * @param exp 表达式
     */
    VarExp(String exp) {
        Objects.requireNonNull(exp);
        if (!(exp.startsWith("${")) || !exp.endsWith("}")) {
            throw new IllegalArgumentException("表达式[" + exp + "]必须类似于${}");
        }

        String[] nodes = exp.substring(2, exp.length() - 1).split(",");
        if (nodes.length > 2) {
            throw new IllegalArgumentException("表达式[" + exp + "]只能出现一个','");
        }

        this.varName = nodes[0].trim();
        this.defaultValue = nodes.length == 2 ? nodes[1].trim() : "";
        this.nullable = exp.startsWith("$");
    }

    @Override
    public String resolve(Map<String, Object> context) {
        Object value = context.get(varName);
        if (value == null && nullable) {
            value = defaultValue == null ? "" : defaultValue;
        }
        if (value == null) {
            throw new NullPointerException("上下文中没有指定的变量:var=" + varName + " map=" + context);
        }
        return value.toString();
    }

    @Override
    public String toString() {
        return "VarExp [varName=" + varName + ", defaultValue=" + defaultValue + ", nullable="
                + nullable + "]";
    }

}
